(define-library (oaxaca detector)

  (export configuration
          set-window-center!
          set-configuration!
          window
          pixel-frequency
          min-frame-frequency
          max-frame-frequency
          frame-frequency
          actual-frame-frequency
          frame-time
          frame
          frames-per-integration
          integration-time
          integration
          integrations-per-exposure
          exposure-time
          z-min
          z-max
          z-mean
          fwhm
          eshm
          spatial-sigma
          temporal-sigma
          search-radius
          set-search-radius!
          refinement-radius
          set-refinement-radius!
          s-vector
          r-vector
          set-r-vector!
          ds-mean-vector
          ds-sigma-vector
          dt-sigma
          i-matrix
          set-i-matrix!
          c-matrix
          set-c-matrix!
          g-vector
          set-g-vector!)

  (import (scheme base)
          (scheme char)
          (scheme time)
          (scheme write))

  (import (oaxaca file)
          (prefix (oaxaca foreign) foreign:)
          (oaxaca log)
          (oaxaca thread)
          (oaxaca wfc))

  (begin

    (define (configuration detector-index)
      (values
       (foreign:detector-window-sx detector-index)
       (foreign:detector-window-sy detector-index)
       (foreign:detector-window-nx detector-index)
       (foreign:detector-window-ny detector-index)
       (foreign:detector-pixel-frequency detector-index)
       (foreign:detector-frame-frequency detector-index)))

    (define detector-nx 1280)
    (define detector-ny 1024)
    (define detector-sx-step 4)
    (define detector-sy-step 4)
    
    (define (set-window-center! detector-index cx cy)
      (foreign:detector-set-window-cx! detector-index cx)
      (foreign:detector-set-window-cy! detector-index cy)
      (values))      

    (define (set-configuration! detector-index
                                directory-path
                                name
                                window-nx
                                window-ny
                                pixel-frequency
                                frame-frequency)
      ;; Select the corresponding dark.
      (let ((dark-file-path (string-append directory-path "/" "dark.fits"))
            (dark-name-file-path (make-path directory-path (string-append "dark-" (symbol->string name) ".fits"))))
        (when (file-exists? dark-file-path)
          (delete-file dark-file-path))
        (when (not (file-exists? dark-name-file-path))
          (close-output-port (open-binary-output-file dark-name-file-path)))
        (link-file dark-name-file-path dark-file-path))
      (let* ((window-cx (foreign:detector-window-cx detector-index))
             (window-cy (foreign:detector-window-cy detector-index))
             (window-sx (- window-cx (quotient window-nx 2)))
             (window-sx (* detector-sx-step (quotient window-sx detector-sx-step)))
             (window-sx (max window-sx 0))
             (window-sx (min window-sx (- detector-nx window-nx)))
             (window-sy (- window-cy (quotient window-ny 2)))
             (window-sy (* detector-sy-step (quotient window-sy detector-sy-step)))
             (window-sy (max window-sy 0))
             (window-sy (min window-sy (- detector-ny window-ny))))
        ;; Offset the r vector.
        (let* ((old-window-sx (foreign:detector-window-sx detector-index))
               (old-window-sy (foreign:detector-window-sy detector-index))
               (dx (- old-window-sx window-sx))
               (dy (- old-window-sy window-sy))
               (old-r-vector (r-vector detector-index))
               (ns (vector-length old-r-vector))
               (r-vector (make-vector ns 0.0)))
          (do ((i 0 (+ i 1)))
              ((= i (/ ns 2)))
            (vector-set! r-vector (+ (* 2 i) 0) (+ (vector-ref old-r-vector (+ (* 2 i) 0)) dx))
            (vector-set! r-vector (+ (* 2 i) 1) (+ (vector-ref old-r-vector (+ (* 2 i) 1)) dy)))
          (set-r-vector! detector-index r-vector))
        ;; Now configure the detector.
        (foreign:detector-set-pixel-frequency! detector-index (inexact pixel-frequency))
        (foreign:detector-set-window-sx! detector-index window-sx)
        (foreign:detector-set-window-sy! detector-index window-sy)
        (foreign:detector-set-window-nx! detector-index window-nx)
        (foreign:detector-set-window-ny! detector-index window-ny)
        (foreign:detector-set-frame-frequency! detector-index (inexact frame-frequency))
        (values)))

    (define (window detector-index)
      (values (foreign:detector-window-sx detector-index)
              (foreign:detector-window-sy detector-index)
              (foreign:detector-window-nx detector-index)
              (foreign:detector-window-ny detector-index)))

    (define (pixel-frequency detector-index)
      (foreign:detector-pixel-frequency detector-index))

    (define (min-frame-frequency detector-index)
      (foreign:detector-min-frame-frequency detector-index))

    (define (max-frame-frequency detector-index)
      (foreign:detector-max-frame-frequency detector-index))

    (define (frame-frequency detector-index)
      (foreign:detector-frame-frequency detector-index))

    (define (actual-frame-frequency detector-index)
      (foreign:detector-actual-frame-frequency detector-index))

    (define (frame-time detector-index)
      (foreign:detector-frame-time detector-index))

    (define (exposure-time detector-index)
      (foreign:detector-exposure-time detector-index))

    (define (integration-time detector-index)
      (foreign:detector-integration-time detector-index))

    (define (integrations-per-exposure detector-index)
      (foreign:detector-integrations-per-exposure detector-index))

    (define (frames-per-integration detector-index)
      (foreign:detector-frames-per-integration detector-index))

    (define (integration detector-index)
      (foreign:detector-integration detector-index))

    (define (frame detector-index)
      (foreign:detector-frame detector-index))

    (define (z-min detector-index)
      (foreign:detector-z-min detector-index))

    (define (z-max detector-index)
      (foreign:detector-z-max detector-index))

    (define (z-mean detector-index)
      (foreign:detector-z-mean detector-index))

    (define (fwhm detector-index)
      (foreign:detector-fwhm detector-index))

    (define (eshm detector-index)
      (foreign:detector-eshm detector-index))

    (define (spatial-sigma detector-index)
      (foreign:detector-spatial-sigma detector-index))

    (define (temporal-sigma detector-index)
      (foreign:detector-temporal-sigma detector-index))

    (define (search-radius detector-index)
      (foreign:detector-search-radius detector-index))

    (define (set-search-radius! detector-index radius)
      (foreign:detector-set-search-radius! detector-index radius))

    (define (refinement-radius detector-index)
      (foreign:detector-refinement-radius detector-index))

    (define (set-refinement-radius! detector-index radius)
      (foreign:detector-set-refinement-radius! detector-index radius))

    (define (s-vector detector-index)
      (let* ((ns (foreign:detector-ns detector-index))
             (v (make-vector ns 0.0)))
        (do ((i 0 (+ i 1)))
            ((= i ns))
          (vector-set! v i (foreign:detector-s-vector-ref detector-index i)))
        v))

    (define (r-vector detector-index)
      (let* ((ns (foreign:detector-ns detector-index))
             (v (make-vector ns 0.0)))
        (do ((i 0 (+ i 1)))
            ((= i ns))
          (vector-set! v i (foreign:detector-r-vector-ref detector-index i)))
        v))

    (define (set-r-vector! detector-index v)
      (when (odd? (vector-length v))
        (error "invalid spot vector."))
      (let ((ns (vector-length v)))
        (when (> ns 64)
          (error "too many spots in spot vector."))
        (foreign:detector-set-ns! detector-index ns)
        (do ((i 0 (+ i 1)))
            ((= i ns))
          (foreign:detector-r-vector-set! detector-index i (vector-ref v i)))
        (values)))

    (define (ds-mean-vector detector-index)
      (let* ((ns (foreign:detector-ns detector-index))
             (v (make-vector ns 0.0)))
        (do ((i 0 (+ i 1)))
            ((= i ns))
          (vector-set! v i (foreign:detector-ds-mean-vector-ref detector-index i)))
        v))

    (define (ds-sigma-vector detector-index)
      (let* ((ns (foreign:detector-ns detector-index))
             (v (make-vector ns 0.0)))
        (do ((i 0 (+ i 1)))
            ((= i ns))
          (vector-set! v i (foreign:detector-ds-sigma-vector-ref detector-index i)))
        v))

    (define (dt-sigma detector-index)
      (foreign:detector-dt-sigma detector-index))

    (define (c-matrix detector-index)
      (define na (vector-length (wfc-a-vector)))
      (define ns (foreign:detector-ns detector-index))
      (define (c-matrix-row i)
        (let ((v (make-vector ns 0.0)))
          (do ((j 0 (+ j 1)))
              ((= j ns))
            (vector-set! v j (foreign:detector-c-matrix-ref detector-index i j)))
          v))
      (let ((v (make-vector na)))
        (do ((i 0 (+ i 1)))
            ((= i na))
          (vector-set! v i (c-matrix-row i)))
        v))

    (define (set-c-matrix! detector-index c-matrix)
      (define na (vector-length (wfc-a-vector)))
      (define ns (foreign:detector-ns detector-index))
      (unless (= na (vector-length c-matrix))
        (error "c-matrix does not match the number of modes."))
      (do ((i 0 (+ i 1)))
          ((= i na))
        (let ((c-matrix-row (vector-ref c-matrix i)))
          (unless (= ns (vector-length c-matrix-row))
            (error "c-matrix does not match the number of spots."))
          (do ((j 0 (+ j 1)))
              ((= j ns))
            (foreign:detector-c-matrix-set! detector-index i j (inexact (vector-ref c-matrix-row j))))))
      (values))

    (define (i-matrix detector-index)
      (define na (vector-length (wfc-a-vector)))
      (define ns (foreign:detector-ns detector-index))
      (define (i-matrix-row i)
        (let ((v (make-vector ns 0.0)))
          (do ((j 0 (+ j 1)))
              ((= j ns))
            (vector-set! v j (foreign:detector-i-matrix-ref detector-index i j)))
          v))
      (let ((v (make-vector na)))
        (do ((i 0 (+ i 1)))
            ((= i na))
          (vector-set! v i (i-matrix-row i)))
        v))

    (define (set-i-matrix! detector-index i-matrix)
      (define na (vector-length (wfc-a-vector)))
      (define ns (foreign:detector-ns detector-index))
      (unless (= na (vector-length i-matrix))
        (error "i-matrix does not match the number of modes."))
      (do ((i 0 (+ i 1)))
          ((= i na))
        (let ((i-matrix-row (vector-ref i-matrix i)))
          (unless (= ns (vector-length i-matrix-row))
            (error "i-matrix does not match the number of spots."))
          (do ((j 0 (+ j 1)))
              ((= j ns))
            (foreign:detector-i-matrix-set! detector-index i j (inexact (vector-ref i-matrix-row j))))))
      (values))

    (define (g-vector detector-index)
      (define na (vector-length (wfc-a-vector)))
      (let ((v (make-vector na 0.0)))
        (do ((i 0 (+ i 1)))
            ((= i na))
          (vector-set! v i (foreign:detector-g-vector-ref detector-index i)))
        v))

    (define (set-g-vector! detector-index v)
      (define na (vector-length (wfc-a-vector)))
      (unless (= na (vector-length v))
        (error "gain does not match the number of modes."))
      (do ((i 0 (+ i 1)))
          ((= i na))
        (foreign:detector-g-vector-set! detector-index i (inexact (vector-ref v i))))
      (values))))

